package org.apache.ocean.name;

import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.apache.ocean.CElement;
import org.apache.ocean.ClusterUtil;
import org.apache.ocean.HttpParameters;
import org.apache.ocean.HttpRequest;
import org.apache.ocean.HttpResponse;
import org.apache.ocean.HttpTimeout;
import org.apache.ocean.OceanXML;
import org.apache.ocean.TimeoutState;
import org.apache.ocean.XML;
import org.apache.ocean.XMLUtil;
import org.jdom.Element;

/**
 *
 * @author jasonr
 */
// TODO: initial file event that contains all files changed
public class FileClient {
  NameClient nameClient;
  Map<String,File> currentFileMap = new HashMap<String,File>();
  List<FileEventListener> fileEventListeners = new ArrayList<FileEventListener>();
  
  public FileClient(NameClient nameClient) {
    this.nameClient = nameClient;
  }
  
  public HttpResponse getFile(String path, HttpTimeout httpTimeout) throws Exception {
  	if (StringUtils.isBlank(path)) throw new IllegalArgumentException("path null");
    URL url = nameClient.getMasterNameURL(httpTimeout.timeoutState);
    HttpParameters parameters = new HttpParameters("action", "getfile", "path", path);
    HttpResponse httpResponse = HttpRequest.get(url, parameters, httpTimeout);
    if (httpResponse.httpMethod.getStatusCode() != 200) {
      throw new Exception("error getting file "+path+" from name server");
    }
    return httpResponse;
  }
  
  public void addFileEventListener(FileEventListener fileEventListener) {
    fileEventListeners.add(fileEventListener);
  }
  
  public void poll(TimeoutState timeoutState) throws Exception {
    HttpTimeout httpTimeout = new HttpTimeout(1000*3, 1000*3, timeoutState);
    List<File> files = getFileListHttp(httpTimeout);
    Map<String,File> serverFileMap = ClusterUtil.reflectToMap("path", files);
    
    Map<String,File> newFiles = new HashMap<String,File>();
    for (Map.Entry<String,File> entry : serverFileMap.entrySet()) {
      if (!currentFileMap.containsKey(entry.getKey())) newFiles.put(entry.getKey(), entry.getValue());
    }
    Map<String,File> removedFiles = new HashMap<String,File>();
    for (Map.Entry<String,File> entry : currentFileMap.entrySet()) {
      if (!serverFileMap.containsKey(entry.getKey())) removedFiles.put(entry.getKey(), entry.getValue());
    }
    Map<String,File> changedFiles = new HashMap<String,File>();
    for (Map.Entry<String,File> currentEntry : currentFileMap.entrySet()) {
      File serverFile = serverFileMap.get(currentEntry.getKey());
      if (serverFile.hasChanged(currentEntry.getValue())) changedFiles.put(serverFile.path, serverFile);
    }
    if (newFiles.size() > 0 || removedFiles.size() > 0 || changedFiles.size() > 0) {
      FileEvent fileEvent = new FileEvent();
      fileEvent.newFiles = newFiles;
      fileEvent.removedFiles = removedFiles;
      fileEvent.changedFiles = changedFiles;
      fireFileEvent(fileEvent);
    }
  }
  
  private void fireFileEvent(FileEvent fileEvent) {
    for (FileEventListener fileEventListener : fileEventListeners) {
      fileEventListener.filesChanged(fileEvent);
    }
  }
  
  public List<File> getFileListHttp(HttpTimeout httpTimeout) throws Exception {
    URL url = nameClient.getMasterNameURL(httpTimeout.timeoutState);
    OceanXML response = OceanXML.request(url, new HttpParameters("action", "getfilelist"), httpTimeout);
    return response.bodyToList(new OceanXML.ReflectTransform(File.class));
  }
  
  public static interface FileEventListener {
    public void filesChanged(FileEvent fileEvent);
  }
  
  public static class FileEvent {
    public Map<String,File> newFiles;
    public Map<String,File> removedFiles;
    public Map<String,File> changedFiles;
    
    public File getFile(String path) {
    	if (newFiles.containsKey(path)) return newFiles.get(path);
    	if (changedFiles.containsKey(path)) return changedFiles.get(path);
    	return null;
    }
    
    public boolean hasChanged(String path) {
      if (newFiles.containsKey(path) || removedFiles.containsKey(path) || changedFiles.containsKey(path)) {
        return true;
      } else return false;
    }
  }
  
  public static class File implements CElement {
    public Date lastModified;
    public String path;
    @XML(exclude=true)
    public FileClient fileClient;
    
    public File() {
    }
    
    public File(Element element) throws Exception {
      XMLUtil.reflectToObject(this, element);
    }
    
    public Element toElement() throws Exception {
      return XMLUtil.reflectToElement("file", this);
    }
    
    public File(FileClient fileClient) {
      this.fileClient = fileClient;
    }
    
    public boolean hasChanged(File other) {
      return !other.lastModified.equals(lastModified);
    }
    
    public HttpResponse get(HttpTimeout httpTimeout) throws Exception {
      URL url = fileClient.nameClient.getMasterNameURL(httpTimeout.timeoutState);
      HttpParameters parameters = new HttpParameters("action", "getfile", "path", path);
      return HttpRequest.get(url, parameters, httpTimeout);
    }
  }
}
